Skip to main content
The Toeplitz module implements efficient Toeplitz matrix multiplication over GF(2) for compressing LPN outputs to 127 bits.

Main function

toep_127()

Multiplies a Toeplitz matrix by a bit vector, returning the first 127 bits.
const std::vector<uint64_t>&
First row and column of the Toeplitz matrix, packed as 64-bit words. Length should be (lpn_t + 127 + 63) / 64 words.
const std::vector<uint64_t>&
Input bit vector (LPN output), packed as 64-bit words. Length should be (lpn_t + 63) / 64 words.
uint64_t&
Output: lower 64 bits of the result
uint64_t&
Output: upper 63 bits of the result (most significant bit should be 0)

Algorithm

A Toeplitz matrix has constant diagonals:
The multiplication z = T * y is computed via GF(2) convolution:
  1. Perform binary polynomial multiplication: R = top ⊗ ybits
  2. Extract bits 0-126 from R as the output
The function automatically selects the fastest available implementation.
This function performs runtime dispatch to choose between scalar, PCLMUL (x86), or PMULL (ARM) implementations based on hardware capabilities.

Implementation variants

toep_127_scalar()

Portable scalar implementation.
Uses gf2_conv_scalar() for binary polynomial multiplication without hardware acceleration.

toep_127_clmul()

Hardware-accelerated implementation using Intel PCLMUL.
Requires:
  • x86-64 architecture
  • PCLMUL instruction support
  • Compile with -mpclmul or -march=native
Uses _mm_clmulepi64_si128 intrinsic for fast carryless multiplication.

toep_127_pmull()

Hardware-accelerated implementation for ARM NEON.
Requires:
  • ARM64 (AArch64) architecture
  • Crypto extensions
  • Compile with -march=armv8-a+crypto
Uses vmull_p64 intrinsic for polynomial multiplication.

Binary polynomial multiplication

gf2_conv_scalar()

Scalar GF(2) convolution.
const std::vector<uint64_t>&
First polynomial (bit-packed)
const std::vector<uint64_t>&
Second polynomial (bit-packed)
std::vector<uint64_t>&
Output: product polynomial with length A.size() + B.size()
Computes R(x) = A(x) · B(x) in GF(2)[x] using the standard schoolbook algorithm:
  • For each set bit at position i in A
  • For each word in B
  • XOR shifted B into R at position i

gf2_conv_clmul()

PCLMUL-accelerated GF(2) convolution.
Same interface as gf2_conv_scalar(), but uses _mm_clmulepi64_si128 for 64×64→128 bit carryless multiplication.

gf2_conv_pmull()

PMULL-accelerated GF(2) convolution for ARM.
Same interface as gf2_conv_scalar(), but uses vmull_p64 for polynomial multiplication on ARM64.

Runtime selection

select_toeplitz()

Benchmarks available implementations and selects the fastest.
This function is called automatically on the first call to toep_127(). It:
  1. Tests all available implementations (scalar, PCLMUL, PMULL)
  2. Benchmarks each on typical inputs
  3. Selects the fastest implementation
  4. Stores the selection in global variable g_toep
The benchmark performs 64 iterations with 4096-bit inputs and measures microseconds per call.
The selection is cached in a global variable, so it only runs once per program execution.

Global variables

Toeplitz matrix properties

Structure

A Toeplitz matrix has the form:
Each diagonal has a constant value.

Universal hashing

Random Toeplitz matrices form a family of universal hash functions:
  • Uniformity: For random T and fixed x ≠ y, Pr[Tx = Ty] ≤ 2^(-127)
  • Compression: Maps lpn_t bits (16384) to 127 bits
  • Efficiency: Computable via fast convolution

Security role

In PVAC-HFHE, Toeplitz hashing serves as a randomness extractor:
  1. LPN outputs lpn_t = 16384 bits with ~11000 bits min-entropy
  2. Toeplitz extraction produces 127 nearly-uniform bits
  3. Result is hashed to a field element via hash_to_fp_nonzero()
This provides statistical security for the PRF output.

Performance characteristics

Benchmarks (approximate, varies by hardware)

Hardware acceleration provides ~7-10× speedup over scalar implementation.

Optimization notes

  • The convolution is computed over (lpn_t + 127) bits
  • Only the first 127 output bits are extracted
  • Sparse inputs (few set bits) benefit from early termination
  • Hardware implementations use SIMD parallelism
Compiling without hardware support flags (e.g., missing -mpclmul) will fall back to scalar mode, significantly reducing performance.

Example usage

Implementation details

Bit extraction

After convolution, the code extracts bits 0-126:
This produces two 64-bit words with the upper word having bit 63 clear.

Intrinsics used

x86-64 PCLMUL:
ARM64 PMULL:
Both instructions compute 64×64→128 bit carryless multiplication in a single cycle.